﻿namespace WebPacker.Packs.Watchers
{
	using System;
	using System.Collections.Generic;
	using System.Threading;
	using WebPacker.Logger;
	using WebPacker.Packs.Resources;

	public class RemoteWatcher : BaseResourceWatcher
	{
		private readonly AsyncCallback updateCallback;
		private Timer timer;
		private int monitorInterval;

		public RemoteWatcher()
		{
			monitorInterval = 1000;
			updateCallback = new AsyncCallback(UpdateCallback);
			timer = new Timer(OnTimer);
			timer.Change(monitorInterval, Timeout.Infinite);
		}

		private delegate bool UpdateDelegate();

		public override event EventHandler<ResourceChangedEventArgs> Changed;

		public int MonitoringInterval
		{
			get
			{
				return monitorInterval;
			}

			set
			{
				if (value < 1)
				{
					throw new ArgumentException("Monitor interval must be positive value.");
				}

				monitorInterval = value;
				timer.Change(value, Timeout.Infinite);
			}
		}

		public override bool Accept(IResource resource)
		{
			return resource is RemoteResource;
		}

		protected override void Dispose(bool disposing)
		{
			if (!IsDisposed)
			{
				timer.Dispose();
			}

			base.Dispose(disposing);
		}

		private void OnTimer(object state)
		{
			if (timer == null)
			{
				return;
			}

			var interval = MonitoringInterval;

			try
			{
				var now = DateTime.Now;
				var waitHandles = new List<WaitHandle>();

				lock (syncRoot)
				{
					foreach (RemoteResource resource in resources)
					{
						var updateMethod = (UpdateDelegate)resource.Update;
						var asyncResult = updateMethod.BeginInvoke(UpdateCallback, new UpdateAsyncState(resource, updateMethod));
						waitHandles.Add(asyncResult.AsyncWaitHandle);
					}
				}

				if (waitHandles.Count > 0)
				{
					WaitHandle.WaitAll(waitHandles.ToArray());
				}

				var duration = (int)(DateTime.Now - now).TotalSeconds;
				interval = Math.Max(1, interval - duration);
			}
			catch (Exception e)
			{
				LogFactory.GetLogger().Exception("Error at monitoring of remote resources.", e);
			}
			finally
			{
				timer.Change(interval, Timeout.Infinite);
			}
		}

		private void UpdateCallback(IAsyncResult result)
		{
			string name = null;

			try
			{
				var asyncState = (UpdateAsyncState)result.AsyncState;
				var resource = asyncState.Resource;
				name = resource.Name;
				var updated = asyncState.UpdateMethod.EndInvoke(result);

				if (updated && Changed != null)
				{
					Changed(this, new ResourceChangedEventArgs(name, new[] { resource }));
				}
			}
			catch (Exception e)
			{
				LogFactory.GetLogger().Exception(string.Format("Error at process change of resource \"{0}\".", name), e);
			}
		}

		private class UpdateAsyncState
		{
			private readonly RemoteResource resource;
			private UpdateDelegate updateMethod;

			public UpdateAsyncState(RemoteResource resource, UpdateDelegate updateMethod)
			{
				this.resource = resource;
				this.updateMethod = updateMethod;
			}

			public RemoteResource Resource
			{
				get { return resource; }
			}

			public UpdateDelegate UpdateMethod
			{
				get { return updateMethod; }
			}
		}
	}
}
